﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using log4net;
using AppFx.BizTalk.IO.Streams.Spec;
using AppFx.BizTalk.Diagnostics;

namespace AppFx.BizTalk.IO.Streams
{
    public class GenericDownloadStream : Stream, IDisposable
    {
        private static readonly ILog Log = LogFactory.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private readonly Queue<byte> _writeQueue;         
        private readonly IDownloadManager _downloadManager;

        public GenericDownloadStream(IDownloadManager downloadManager)
        {
            _downloadManager = downloadManager;
            _writeQueue = new Queue<byte>();
        }


        public override int Read(byte[] buffer, int offset, int count)
        {
            Log.Debug("Executing Read");
            Log.DebugFormat("Buffer size - {0}", buffer.Length);
            Log.DebugFormat("Count - {0}", count);
            Log.DebugFormat("Offset - {0}", offset);
            Log.DebugFormat("Write Queue Length - {0}", _writeQueue.Count);

            int bytesWritten = 0;//number of bytes written to output buffer; 
            var thisRead = new List<byte>();

            //first of all - use any 'left over bytes' from previous read (left over bytes will exist if buffer was smaller than any one line so we could not return the entire line to the caller)
            CopyLeftOver(thisRead, count);
            Log.DebugFormat("Bytes copied from left over - {0}", thisRead.Count);

            int readCount = thisRead.Count;
            while (readCount < count)
            {
                Log.Debug("Calling download manager to get more data");

                var nextPage = _downloadManager.GetNext();
                if (nextPage.Length == 0)
                {
                    Log.Debug("No more data to retrieve from data source");
                    break;
                }

                //Add data to write queue
                foreach(var nextByte in nextPage)
                    _writeQueue.Enqueue(nextByte);

                readCount += nextPage.Length;
            }

            //Get data from write queue
            CopyLeftOver(thisRead, count);

            bytesWritten = thisRead.Count;
            offset += bytesWritten;
            Buffer.BlockCopy(thisRead.ToArray(), 0, buffer, 0, thisRead.Count);
            return bytesWritten;
        }

        private void CopyLeftOver(List<byte> thisRead, int count)
        {
            Log.Debug("Executing CopyLeftOver");
            Log.DebugFormat("thisRead Size - {0}", thisRead.Count);
            Log.DebugFormat("Count - {0}", count);

            var remaining = count - thisRead.Count;

            //The buffer is already full
            if (remaining == 0)
                return;

            //if we have no left over bytes
            if(_writeQueue.Count == 0)
                return;

            //if we have place for the entire 'left over'
            if (remaining >= _writeQueue.Count)
            {
                for (var index = 0; index < remaining; index++)
                {
                    if (_writeQueue.Count == 0)
                        break;

                    thisRead.Add(_writeQueue.Dequeue());
                }
            }

            //if we don't have place for the entire left over
            if (remaining < _writeQueue.Count)
            {
                for (var index = 0; index < remaining; index++)
                    thisRead.Add(_writeQueue.Dequeue());
            }
        }
        
        #region Stream overrides
        public override bool CanSeek
        {
            get { 
                //as the stream is bufferring, additional code would have been required to support seeking 
                //(mostly getting read of the buffer on seek), but this is not needed for the BizTalk scenario 
                //(BizTalk itself will never seek the stream), so I've avoided adding this code
                return false; }
        }
        public override long Position
        {
            get
            {
                return 0;
                //return _inputStream.Position;
            }
            set
            {
                throw new NotSupportedException("this is not seekable");
            }
        }
        public override bool CanWrite
        {
            get
            {
                return false; //stream is non-writable due to the way it buffers data, but this is not a requirement usually in this context
            }
        }
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException("this is not seekable");
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException("this is not writable");
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException("this is not writable");
        }

        #endregion

        #region Stream overrides - all of these are forwarded to the underlying stream
        public override bool CanRead
        {
            get 
            { 
                return true;
            }
        }

        public override void Flush()
        {
            
        }

        public override long Length
        {
            get 
            { 
                return 0;
            }
        }

        #endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            Close();
        }

        #endregion

    } 
}

